package mango.orm.connect;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

import mango.Mango;
import mango.functions.FuncString;
import mango.orm.OrmException;


public final class Connector {
    public static final String DB_ORACLE = "Oracle";
    public static final String DB_SQLSERVER = "SQLServer";
    public static final String DB_MYSQL = "MYSQL";
    public static final String DB_DB2 = "DB2";

    private static final List<String> REGIESTED_DRIVER = new ArrayList<String>();
    private static HashMap<String, DataSource> DATASOURCES = new HashMap<String, DataSource>();
    private ConnectionParam connParam;
    private Connection conn;
    private String connName;
    

    public static Connector getConnector() {
        return getConnector(Mango.getProperty(Mango.CONNECTOR));
    }

    public static Connector getConnector(String connName) {
        ConnectionParam param = new ConnectionParam(connName);
        return new Connector(param);
    }
    
    private Connector(ConnectionParam param) {
        this.connName = param.getConnName();
        this.connParam = param;
        this.validate();
    }

    private void validate() {
        try {
            if (FuncString.isBlank(this.connParam.getDbType())) {
                throw new OrmException("Connector : " + this.connParam.getConnName() + " no database type!");
            }
            else if (FuncString.containIgnoreCase(this.connParam.getDbType(), DB_ORACLE)
                    ||FuncString.containIgnoreCase(this.connParam.getDbType(), DB_MYSQL)) {

            }
            else {
                throw new OrmException("Connector : " + this.connParam.getConnName()
                        + " Unsupported database type!");
            }

            if (!this.connParam.isJndiconn()) {
                if (FuncString.isBlank(this.connParam.getUrl())) {
                    throw new OrmException("Connector : " + this.connParam.getConnName() + " no connection url!");
                }
                if (FuncString.isBlank(this.connParam.getDriver())) {
                    throw new OrmException("Connector : " + this.connParam.getConnName()
                            + " no connection jdbc driver !");
                }
                else {
                    if (!REGIESTED_DRIVER.contains(this.connParam.getDriver())) {
                        synchronized (REGIESTED_DRIVER) {
                            if (!REGIESTED_DRIVER.contains(this.connParam.getDriver())) {
                                Class.forName(this.connParam.getDriver());
                                REGIESTED_DRIVER.add(this.connParam.getDriver());
                            }
                        }
                    }
                }
            }
        }
        catch (ClassNotFoundException e) {
            throw new OrmException(e);
        }
    }

    public String getDbType() {
        return this.connParam.getDbType();
    }

    public String getConnName() {
        return connName;
    }

    public Connection getConnection() {
        if (this.conn != null) {
            return this.conn;
        }
        try {
            if (DATASOURCES.get(this.connName) == null) {
                synchronized (DATASOURCES) {
                    if (DATASOURCES.get(this.connName) == null) {
                        if (this.connParam.isJndiconn()) {
                            Context ctx = new InitialContext();
                            Object o = ctx.lookup(this.connParam.getJndi());
                            if (o == null) {
                                throw new OrmException("Connector : " + this.connName
                                        + " can't lookup the jndi : " + this.connParam.getJndi());
                            }
                            DATASOURCES.put(this.connName, (DataSource) o);
                        }
                        else {
                            Connection temp = DriverManager.getConnection(this.connParam.getUrl(), this.connParam.getUser(),
                                    this.connParam.getPassword());
                            temp.close();
                            DataSource ds = new _DataSource(this.connParam);
                            DATASOURCES.put(this.connName, ds);
                        }
                    }
                }
            }
            if (DATASOURCES.get(this.connName) != null) {
                this.conn = DATASOURCES.get(this.connName).getConnection();
                return this.conn;
            }
        }
        catch (Exception e) {
            throw new OrmException("Connector : " + this.connName + " cannot build connection!", e);
        }
        throw new OrmException("Connector : " + this.connName + " cannot build connection!");
    }

    public void startTransaction() {
        if (this.conn == null) {
            this.getConnection();
        }
        try {
            if (isTransactionSupported()) {
                this.conn.setAutoCommit(false);
            }
        }
        catch (SQLException e) {
            try {
                this.conn.close();
            }
            catch (SQLException e1) {
            }
            throw new OrmException(e);
        }
    }

    public void commit() throws SQLException {
        if (this.conn != null) {
            if (isTransactionSupported()) {
                this.conn.commit();
            }
        }
    }

    public void rollback() throws SQLException {
        if (this.conn != null) {
            if (isTransactionSupported()) {
                this.conn.rollback();
            }
        }
    }

    public boolean isTransactionSupported() {
        if (FuncString.containIgnoreCase(this.connParam.getDbType(), DB_ORACLE)
            || FuncString.containIgnoreCase(this.connParam.getDbType(), DB_MYSQL)) {
            return true;
        }
        else {
            // ...
            return false;
        }
    }

    public void close() throws SQLException {
        if (this.conn != null) {
            this.conn.close();
            this.conn = null;
        }
    }
    
    public static void closeAll() {
        for (Iterator<DataSource> iter = DATASOURCES.values().iterator(); iter.hasNext();) {
            DataSource ds = iter.next();
            if (ds instanceof _DataSource) {
                _DataSource d = (_DataSource) ds;
                d.close();
            }
        }
    }
}
